// Author:
// Date:
// UNSW Slide assignment.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define SIZE 15
#define EMPTY 0
#define STONE 1
#define WIN 1
#define LOSS -1

void printMap(int map[SIZE][SIZE], int laserY);
int moveLaser(int laserY, int value);
int fireLaser(int map[SIZE][SIZE], int laserY);
int shiftLeft(int shiftIndex, int map[SIZE][SIZE]);
int rotateMap(int rotateUsed, int map[SIZE][SIZE], int degree);
int gravityStorm(int gravityStormUsed, int map[SIZE][SIZE], int value);
void clockwiseRotate(int map[SIZE][SIZE]);
void counterClockwiseRotate(int map[SIZE][SIZE]);

void blockToBottom(int map[SIZE][SIZE]);
void blockToBottomRight(int map[SIZE][SIZE]);
void blockToTopRight(int map[SIZE][SIZE]);
void blockToTop(int map[SIZE][SIZE]);


int main (void) {
    // This line creates our 2D array called "map" and sets all
    // of the blocks in the map to EMPTY.
    int map[SIZE][SIZE] = {EMPTY};
    int i = 0, x = 0, y = 0, value = 0;
    int cmd = 0, result = 0;
    
    // This line creates out laserY variable. The laser starts in the
    // middle of the map, at position 7.
    int laserY = SIZE / 2;
    // Block shift rule: 0 left, 1 left, 2 down, 3 left, 4 left, 5 up
    int shiftIndex = 0;
    int maxRowBlock = 0;
    
    // RotateFlag: 0: not used, 1 used
    int rotateUsed = 0;
    
    // GravityStormFlag: 0: not used, 1 used
    int gravityStormUsed = 0;
    
    
    printf("How many blocks? ");
    //Scan in the number of blocks.
    scanf("%d", &maxRowBlock);
    
    printf("Enter blocks:\n");
    //Scan in the blocks.
    for (i = 0; i < maxRowBlock; i++) {
        scanf("%d %d %d", &x, &y, &value);
        // check the value
        if (x >= SIZE || y >= SIZE || x < 0 || y < 0) {
            continue;
        }
        if (value > 9 || value < 1) {
            continue;
        }
        
        //set the block value
        map[x][y] = value;
    }
    
    //Placing Stones
    printMap(map, laserY);
    
    // After each command is processed, you should call printMap.
    value = 0;
    while (1) {
        scanf("%d", &cmd);
        switch (cmd) {
            case 1: //Moving the Laser
                scanf("%d", &value);
                laserY += moveLaser(laserY, value);
                printMap(map, laserY);
                break;
                
            case 2: //Firing the Laser
                result = fireLaser(map, laserY);
                printMap(map, laserY);
                // check command result
                if (result == WIN) {
                    printf("Game Won!\n");
                    return 0;
                }
                break;
                
            case 3: //Shift Everything Left
                shiftIndex = shiftLeft(shiftIndex, map);
                printMap(map, laserY);
                // check command result
                if (shiftIndex == LOSS) {
                    printf("Game Lost!\n");
                    return 0;
                }
                break;
                
            case 4: //Rotate Map
                scanf("%d", &value);
                rotateUsed = rotateMap(rotateUsed, map, value);
                printMap(map, laserY);
                break;
                
            case 5: //Gravity Storms
                scanf("%d", &value);
                gravityStormUsed = gravityStorm(gravityStormUsed, map, value);
                printMap(map, laserY);
                break;
            default:
                break;
        }
    }
    
    return 0;
}

// Command: move laser
int moveLaser(int laserY, int value) {
    if (value != 1 && value != -1) {
        return 0;
    }
    
    // check first row
    if (laserY == 0 && value == -1) {
        return 0;
    }
    
    // check last row
    if (laserY == (SIZE - 1) && value == 1) {
        return 0;
    }
    
    return value;
}

// Check if block in tnt range
double getDistance(int cx, int cy, int x, int y) {
    return sqrt(pow(abs(x - cx), 2) + pow(abs(y - cy), 2));
}

// Fire TNT block
void fireTNTBlock(int map[SIZE][SIZE], int tntValue, int y, int x) {
    int x0, y0, x1, y1, distance;
    // printf("(%d,%d)=%d", y, x, tntValue);
    
    // find the min and max index of tnt range
    x0 = x > tntValue ? (x - tntValue) : 0;
    y0 = y > tntValue ? (y - tntValue) : 0;
    x1 = x + tntValue;
    if (x1 > SIZE) {
        x1 = SIZE;
    }
    y1 = y + tntValue;
    if (y1 > SIZE) {
        y1 = SIZE;
    }
    
    int i = y0 - 1, j;
    while (++i < y1) {
        j = x0 - 1;
        while (++j < x1) {
            distance = getDistance(y, x, i, j);
            if (tntValue > distance) {
                map[i][j] = 0;
            }
        }
    }
}

// Command: fire laser
// 1-2: normal block, 3-9: tnt block
int fireLaser(int map[SIZE][SIZE], int laserY) {
    int i = -1, j;
    int pos = 0;
    int fireUpValue = 0,fireUpY = 0,fireUpX = 0;
    int fireRightValue = 0,fireRightY = 0, fireRightX = 0;
    int fireDownValue = 0, fireDownY = 0, fireDownX = 0;
    
    //find the non-empty block
    j = -1;
    while (++j < SIZE) {
        if (map[laserY][j] > 0) {
            break;
        }
    }
    
    if (j < SIZE) {
        // found a non-empty block, fire the block
        if (map[laserY][j] > 2) {
            // if tnt ignore aroud
            fireTNTBlock(map, map[laserY][j], laserY, j);
        } else {
            map[laserY][j] = 0;
            
            // check around
            pos = laserY - 1;
            if (pos >=0 && map[pos][j] > 0) { // up
                fireUpValue = map[pos][j];
                fireUpY = pos;
                fireUpX = j;
            }
            
            pos = laserY + 1;
            if (pos < SIZE && map[pos][j] > 0) { // down
                fireDownValue = map[pos][j];
                fireDownY = pos;
                fireDownX = j;
            }
            
            pos = j + 1;
            if (pos < SIZE && map[laserY][pos] > 0) { // right
                fireRightValue = map[laserY][pos];
                fireRightY = laserY;
                fireRightX = pos;
            }
            
            
            if (fireUpValue > 2) {
                fireTNTBlock(map, fireUpValue, fireUpY, fireUpX);
            } else if (fireUpValue > 0) {
                map[fireUpY][fireUpX] = 0;
            }
            
            if (fireRightValue > 2) {
                fireTNTBlock(map, fireRightValue, fireRightY, fireRightX);
            } else if (fireRightValue > 0) {
                map[fireRightY][fireRightX] = 0;
            }
            
            if (fireDownValue > 2) {
                fireTNTBlock(map, fireDownValue, fireDownY, fireDownX);
            } else if (fireDownValue > 0) {
                map[fireDownY][fireDownX] = 0;
            }
        }
    }
    
    
    // check if win
    i = -1;
    while (++i < SIZE) {
        j = -1;
        while (++j < SIZE) {
            if (map[i][j] > 0) {
                return 0;
            }
        }
    }
    return WIN;
}

// Command: shift left
// Block shift rule: 0 left, 1 left, 2 down, 3 left, 4 left, 5 up
int shiftLeft(int shiftIndex, int map[SIZE][SIZE]) {
    int i = SIZE, j;
    int pos = 0;
    
    if (shiftIndex == 2) {
        // shift let all
        while (--i >= 0) {
            j = -1;
            pos = i + 1;
            
            while (++j < SIZE) {
                if (j == 0) {
                    if (map[i][j] == 0) {
                        continue;
                    }
                    
                    if (map[i][j] == 2) {
                        // shift down 2
                        if (shiftIndex == 2 && pos < SIZE) {
                            map[i - 1][j] = map[i][j];
                            map[i][j] = 0;
                            continue;
                        }
                    }
                    
                    return LOSS;
                } else {
                    if (map[i][j] == 0) {
                        continue;
                    }
                    
                    if (map[i][j] == 2) {
                        
                        // shift down 2
                        if (shiftIndex == 2) {
                            if (pos < SIZE) {
                                map[i + 1][j] = map[i][j];
                            }
                            map[i][j] = 0;
                            continue;
                            
                        }
                        // shift left
                        map[i][j - 1] = map[i][j];
                    }
                    // if shift pos is 2, do not overide
                    if (map[i][j - 1] != 2) {
                        map[i][j - 1] = map[i][j];
                    }
                }
                
                map[i][j] = 0;
            }
        }
    } else {
        // shift let all
        i = -1;
        while (++i < SIZE) {
            j = -1;
            while (++j < SIZE) {
                if (j == 0) {
                    if (map[i][j] == 0) {
                        continue;
                    }
                    
                    if (map[i][j] == 2) {
                        // shift up 2
                        if (shiftIndex == 5 && i > 0) {
                            map[i + 1][j] = map[i][j];
                            map[i][j] = 0;
                            continue;
                        }
                    }
                    
                    return LOSS;
                } else {
                    if (map[i][j] == 0) {
                        continue;
                    }
                    
                    if (map[i][j] == 2) {
                        // shift up 2
                        if (shiftIndex == 5) {
                            if (i > 0) {
                                map[i - 1][j] = map[i][j];
                            }
                            map[i][j] = 0;
                            continue;
                        }
                        // shift left
                        map[i][j - 1] = map[i][j];
                    }
                    // if shift pos is 2, do not overide
                    if (map[i][j - 1] != 2) {
                        map[i][j - 1] = map[i][j];
                    }
                }
                
                map[i][j] = 0;
            }
        }
    }
    
    shiftIndex = (shiftIndex + 1) % 6;
    return shiftIndex;
}

// Command: rotate map
// 1 Rotate the map clockwise by 90 degrees.
// 2 Rotate the map counter-clockwise by 90 degrees.
int rotateMap(int rotateUsed, int map[SIZE][SIZE], int degrees) {
    if (rotateUsed) {
        // Like the rotate map command, this command will only work the first time.
        //Successive attempts to give a gravity storm command should not make any changes.
        return 1;
    }
    switch (degrees) {
        case 1: // clockwiseRotate
            clockwiseRotate(map);
            //printf("clockwiseRotate\n");
            break;
        case 2: // counterClockwiseRotate
            counterClockwiseRotate(map);
            //printf("counterClockwiseRotate\n");
            break;
    }
    return 1;
}

// Command: Rotate the map clockwise by 90 degrees.
void clockwiseRotate(int map[SIZE][SIZE]) {
    int temp[SIZE][SIZE], i = 0,  j = 0;
    
    while (i < SIZE) { // every column
        while (j < SIZE) { // every row
            temp[j][SIZE - 1 - i] = map[i][j];
            j++;
        }
        j=0;
        i++;
    }
    
    i = 0, j = 0;
    
    while (i < SIZE) { // every column
        while (j < SIZE) { // every row
            map[i][j]=temp[i][j];
            j++;
        }
        j=0;
        i++;
    }
}

// Command: Rotate the map counter-clockwise by 90 degrees.
void counterClockwiseRotate(int map[SIZE][SIZE]) {
    int temp[SIZE][SIZE], i = 0,  j = 0;
    
    while (i < SIZE) { // every column
        while (j < SIZE) { // every row
            temp[SIZE - 1 - j][i] = map[i][j];
            j++;
        }
        j=0;
        i++;
    }
    
    i = 0, j = 0;
    while (i < SIZE) { // every column
        while (j < SIZE) { // every row
            map[i][j]=temp[i][j];
            j++;
        }
        j=0;
        i++;
    }
}


// Command: gravity storms and marching blocks.
int gravityStorm(int gravityStormUsed, int map[SIZE][SIZE], int value) {
    if (gravityStormUsed) {
        // If the first gravity storm command is invalid,
        // it will still count as a gravity storm
        // and the user will be unable to give a gravity storm command again.
        return 1;
    }
    switch (value) {
        case 1: // towards the bottom
            blockToBottom(map);
            break;
        case 2: // towards the bottom
            blockToBottomRight(map);
            break;
        case 3: // towards the bottom
            blockToTopRight(map);
            break;
        case 4: // towards the bottom
            blockToTop(map);
            break;
        default:
            break;
    }
    return 1;
}

// Gravity storm to bottom
void blockToBottom(int map[SIZE][SIZE]) {
    int i = -1, j, currentPosition = SIZE - 1;
    while (++i < SIZE) { // every column
        
        // find the last non-empty block
        j = SIZE - 1;
        while (j >= 0) { // every row
            if (map[j][i] > 0) {
                j--;
            }
            break;
        }
        currentPosition = j;
        
        // shift blocks
        j = currentPosition - 1;
        while (j >= 0) {
            if (map[j][i] > 0) {
                map[currentPosition][i] = map[j][i];
                map[j][i] = 0;
                currentPosition--;
            }
            j--;
        }
    }
}

// Gravity storm to bottom-right
void blockToBottomRight(int map[SIZE][SIZE]) {
    int i = SIZE - 1, j, k;
    while (--i >= 0) { // every row
        j = SIZE - 1;
        while (--j >= 0) { // every column
            k = 1;
            while (1) {
                // find bottom-right non-block
                if ((i + k) == SIZE || (j + k) == SIZE || map[i + k][j + k] > 0) {
                    break;
                }
                ++k;
            }
            if (k > 1) {
                --k;
                map[i + k][j + k] = map[i][j];
                map[i][j] = 0;
            }
        }
    }
}

// Gravity storm to top-right
void blockToTopRight(int map[SIZE][SIZE]) {
    int i = 0, j, k;
    while (++i < SIZE) { // every row
        j = SIZE - 1;
        while (--j >= 0) { // every column
            k = 1;
            while (1) {
                // find bottom-right non-block
                if ((i - k) == SIZE || (j + k) == SIZE || map[i - k][j + k] > 0) {
                    break;
                }
                ++k;
            }
            if (k > 1) {
                --k;
                map[i - k][j + k] = map[i][j];
                map[i][j] = 0;
            }
        }
    }
}

// Gravity storm to bottom
void blockToTop(int map[SIZE][SIZE]) {
    int i = -1, j, currentPosition = 0;
    while (++i < SIZE) { // every column
        
        // find the last non-empty block
        j = 0;
        while (j < SIZE) { // every row
            if (map[j][i] > 0) {
                j++;
            }
            break;
        }
        currentPosition = j;
        
        // shift blocks
        j = currentPosition + 1;
        while (j < SIZE) {
            if (map[j][i] > 0) {
                map[currentPosition][i] = map[j][i];
                map[j][i] = 0;
                currentPosition++;
            }
            j++;
        }
    }
}

// Print out the contents of the map array.
// Also print out a > symbol to denote the current laser position.
void printMap(int map[SIZE][SIZE], int laserY) {
    int i = 0;
    while (i < SIZE) {
        if (i == laserY) {
            printf("> ");
        } else {
            printf("  ");
        }
        int j = 0;
        while (j < SIZE) {
            printf("%d ", map[i][j]);
            j++;
        }
        printf("\n");
        i++;
    }
}


